package com.example.retry.service;

import java.time.LocalTime;
import java.util.UUID;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import com.google.gson.Gson;

@Aspect
@Component
public class RetryableLogAspect {
	private static final Logger log = LoggerFactory.getLogger(RetryableLogAspect.class);
	private static final Gson gson = new Gson();

	public static final Integer INITINAL_NUM_ATTEMPTS = Integer.valueOf(1);
	private static ThreadLocal<Integer> numAttemptsThreadLocal = ThreadLocal.withInitial(() -> INITINAL_NUM_ATTEMPTS); // 用于记录当前是第几次重试
	private static ThreadLocal<String> uuidThreadLocal = ThreadLocal
			.withInitial(() -> UUID.randomUUID().toString().replaceAll("-", "")); // 用于唯一标识某个线程

	@Around("@annotation(anno)")
	public Object logAroundRetryable(ProceedingJoinPoint pjp, Retryable anno) throws Throwable {
		Integer numAttempts = numAttemptsThreadLocal.get();
		String uuid = uuidThreadLocal.get();
		log.info("第{}次尝试开始：线程信息={}，当前时间={}，入参={}。", numAttempts,
				Thread.currentThread().getName() + "-" + uuid, LocalTime.now(),
				gson.toJson(pjp.getArgs()));
		numAttemptsThreadLocal.set(numAttempts + 1);
		if (numAttempts >= anno.maxAttempts()) {
			// 执行到最大次数时，重置threadLocal变量；避免执行最后一次尝试仍然抛出异常，无法重置threadLocal变量的问题
			resetThreadLocalVariable();
		}

		Object result = pjp.proceed();

		// 抛出异常则不记录下面的日志
		log.info("第{}次尝试结束，结果是成功：线程信息={}，当前时间={}，入参={}，返回值={}。", numAttempts,
				Thread.currentThread().getName() + "-" + uuid, LocalTime.now(),
				gson.toJson(pjp.getArgs()), gson.toJson(result));
		resetThreadLocalVariable();
		return result;
	}

	private void resetThreadLocalVariable() {
		numAttemptsThreadLocal.remove();
		uuidThreadLocal.remove();
	}

	// @Recover上的切面不会起作用
	@Around("@annotation(org.springframework.retry.annotation.Recover)")
	public Object logAfterRecover(ProceedingJoinPoint pjp) throws Throwable {
		Integer attemptCnt = numAttemptsThreadLocal.get();
		Object result;
		try {
			result = pjp.proceed();
		} finally {
			log.info("第{}次尝试结束，结果是成功：线程信息={}，当前时间={}，异常和入参信息={}。", attemptCnt,
					Thread.currentThread().getName() + "-" + uuidThreadLocal.get(), LocalTime.now(),
					gson.toJson(pjp.getArgs()));
			resetThreadLocalVariable();
		}
		return result;
	}

}
